﻿using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.PlatformAbstractions;
using Microsoft.Extensions.Primitives;
using Sino.CapacityCloud.Repositories.Models;
using Sino.CapacityCloud.Services;
using Sino.Common.Utilities;
using Sino.WebApi.Framework.Core.Filters;
using Sino.WebApi.Framework.Responses;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace Sino.CapacityCloud.WebApi.ApiControllers
{

    public class LoginController
        : ApiController<LoginController>
    {

        private readonly ISmsSender _smsSender;
        private readonly ICaptchasService _captchasService;
        private readonly ITokenGenertationService _tokenGenerator;
        private readonly IChattingService _chattingService;
        private readonly IRandomPasswordService _randomPasswordService;

        public string DefaultAdministratorUserName
        {
            get
            {
                return this.Configuration["Administrator:DefaultAdminUsername"];
            }
        }

        public string DefaultAdministratorPassword
        {
            get
            {
                return this.Configuration["Administrator:DefaultAdminPassword"];
            }
        }

        public string DefaultAdminPhoneNumber
        {
            get
            {
                return this.Configuration["Administrator:DefaultAdminPhoneNumber"];
            }
        }

        public LoginController(
            IHostingEnvironment env,
            IApplicationEnvironment applicationEnvironment,
            IDistributedCache cache,
            ITokenGenertationService tokenGenertationService,
            ApplicationDbContext dbContext,
            ApplicationUserManager userManager,
            ApplicationRoleManager roleManager,
            ApplicationSignInManager signInManager,
            ISmsSender smsSender,
            ICaptchasService captchasService,
            ILoggerFactory loggerFactory,
            ITokenGenertationService tokenGenerator,
            IRandomPasswordService randomPasswordService,
            IChattingService chattingService)
            : base(env, applicationEnvironment, cache, tokenGenertationService, dbContext, userManager, roleManager, signInManager, loggerFactory)
        {
            this._smsSender = smsSender;
            this._captchasService = captchasService;
            this._tokenGenerator = tokenGenerator;
            this._randomPasswordService = randomPasswordService;
            this._chattingService = chattingService;
        }

        [SupressTokenValidation()]
        public async Task<JsonResult> Auth(string token)
        {
            try
            {
                //this._logger.LogInformation($"Calling [{nameof(Auth)}] with [token = {token}]...");
                AuthResponse response = null;
                if (await this._tokenGenerator.ValidateAsync(token))
                {
                    var user = this.DataContext.Users.FirstOrDefault(x => x.Token == token);
                    if (user != null)
                    {
                        var currentTime = DateTime.Now;
                        var expireTime = currentTime + this._tokenGenertationService.Expire;
                        token = await this._tokenGenertationService.GenerateAsync(currentTime, token);
                        user.Token = token;
                        await this.UserManager.UpdateAsync(user);
                        response = new AuthResponse()
                        {
                            ExpirationDate = expireTime,
                            IsAvailable = true,
                            Info = new LoginInfo()
                            {
                                Token = token,
                                TokenExpirationDate = expireTime,
                                UserName = user.PhoneNumber,
                                PhoneNumber = user.PhoneNumber,
                                UserId = user.Id,
                                RealNameState = user.GetRealNameInfo().State,
                                HeadImage = user.AvatarKey,
                                IMToken = await this._chattingService.GenerateTokenAsync(user.Id, user.RealName ?? user.PhoneNumber, user.AvatarKey),
                                Point = user.Points.FirstOrDefault(x => x.UserId == user.Id)?.TotalPoints ?? 0.0f
                            }
                        };
                        return await this.PackageResultAsync(response);
                    }
                }
                response = new AuthResponse()
                {
                    IsAvailable = false
                };
                return await this.PackageResultAsync(response);
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to authenticate because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<AuthResponse>(ex);
            }
        }

        [SupressTokenValidation()]
        public async Task<JsonResult> GetCaptchas(string phoneNumber)
        {
            try
            {
                //this._logger.LogInformation($"Calling [{nameof(GetCaptchas)}] with [phoneNumber = {phoneNumber}]...");
                if ((await this.ShouldSkipAthenticationAsync(phoneNumber)) || await this._captchasService.SendCaptchasAsync(phoneNumber))
                {
                    return await this.PackageResultAsync<VoidResponse>();
                }
                else
                {
                    throw new SystemException("Failed to send the sms.");
                }
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to send the sms because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<VoidResponse>(ex);
            }
        }

        [SupressTokenValidation()]
        public async Task<JsonResult> Login(string phoneNumber, string captchas)
        {
            try
            {
                //this._logger.LogInformation($"Calling [{nameof(Login)}] with [phoneNumber = {phoneNumber}] and [captchas = {captchas}]...");
                // Check if the given phone number is assouciated with an existed user.
                if (!phoneNumber.IsPhoneNumber())
                {
                    throw new ArgumentOutOfRangeException("phoneNumber");
                }
                IdentityResult identityResult = null;
                var user = await this.UserManager.FindByPhoneNumberAsync(phoneNumber);
                if (user == null)
                {
                    // If the user is not found, then we should register him or her fist.
                    var password = await this._randomPasswordService.GenerateRandomPasswordAsync();
                    user = new ApplicationUser()
                    {
                        PhoneNumber = phoneNumber,
                        UserName = phoneNumber.ToString(),
                        CertificatePhotoCertificationState = CertificationStatus.Uncommitted,
                        DrivingLicenseKeyCertificationState = CertificationStatus.Uncommitted,
                        IdentityCardCertificationState = CertificationStatus.Uncommitted
                    };
                    identityResult = await this.UserManager.CreateAsync(user, password);
                    if (!identityResult.Succeeded)
                    {
                        throw new SystemException($"Failed to set role to the user {phoneNumber} with: \n{string.Join("\n", identityResult.ToErrorStrings())}.");
                    }
                    if (!await this.RoleManager.RoleExistsAsync(ApplicationRole.Driver))
                    {
                        this._logger.LogWarning($"Missing '{ApplicationRole.Driver}' role. Adding '{ApplicationRole.Driver}' role...");
                        identityResult = await this.RoleManager.CreateAsync(new ApplicationRole(ApplicationRole.Driver));
                        if (!identityResult.Succeeded)
                        {
                            throw new SystemException($"Failed to add '{ApplicationRole.Driver}' role with: \n{string.Join("\n", identityResult.ToErrorStrings())}.");
                        }
                    }
                    identityResult = await this.UserManager.AddToRoleAsync(user, ApplicationRole.Driver);
                    if (!identityResult.Succeeded)
                    {
                        throw new SystemException($"Failed to add user {user.Id} to '{ApplicationRole.Driver}' role with: \n{string.Join("\n", identityResult.ToErrorStrings())}.");
                    }
                }

                if (!user.LockoutEnabled)
                {
                    throw new UnauthorizedAccessException($"The user {user.Id} is locked out.");
                }

                // The user and role is sure existed now, let's check the user role.
                //if (!await this.UserManager.IsInRoleAsync(user, ApplicationRole.Driver))
                //{
                //    throw new UnauthorizedAccessException($"The user is not of {ApplicationRole.Driver} role.");
                //}
                if (!this._hostingEnvironment.IsDevelopment())
                {
                    if (this._hostingEnvironment.IsStaging())
                    {
                        if (captchas != "2020" && !(await this.ShouldSkipAthenticationAsync(phoneNumber, user)))
                        {
                            throw new UnauthorizedAccessException($"The user {phoneNumber} failed to login with captchas {captchas}.");
                        }
                    }
                    else
                    {
                        var signinResult = await this.SignInManager.CaptchasSignInAsync(user, captchas, false, false);
                        if (!signinResult.Succeeded)
                        {
                            if (!(await this.ShouldSkipAthenticationAsync(phoneNumber, user)))
                            {
                                throw new UnauthorizedAccessException($"The user {phoneNumber} failed to login with captchas {captchas}.");
                            }
                        }
                    }
                }
                var now = DateTime.Now;
                var token = await this._tokenGenerator.GenerateAsync(now);
                user.Token = token;
                await this.UserManager.UpdateAsync(user);
                var response = new LoginResponse()
                {
                    Info = new LoginInfo()
                    {
                        Token = token,
                        TokenExpirationDate = now + this._tokenGenerator.Expire,
                        UserName = user.PhoneNumber,
                        UserId = user.Id,
                        PhoneNumber = user.PhoneNumber,
                        RealNameState = user.GetRealNameInfo().State,
                        HeadImage = user.AvatarKey,
                        IMToken = await this._chattingService.GenerateTokenAsync(user.Id, user.RealName ?? user.PhoneNumber, user.AvatarKey),
                        Point = user.Points.FirstOrDefault(x => x.UserId == user.Id)?.TotalPoints ?? 0.0f
                    }
                };
                return await this.PackageResultAsync(response);
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex.Message, ex);
                return await this.PackageResultAsync<LoginResponse>(ex);
            }
        }

        [HttpPost]
        public async Task<JsonResult> SetAccountLockoutState(string userId, bool value)
        {
            try
            {
                //this._logger.LogInformation($"Calling [{nameof(CloseAccount)}] with [phoneNumber = {phoneNumber}] and [captchas = {captchas}]...");
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user == null)
                {
                    throw new UnauthorizedAccessException($"Failed to find the user {userId}.");
                }

                value = !value;

                var identityResult = await this.UserManager.SetLockoutEnabledAsync(user, value);
                if (!identityResult.Succeeded)
                {
                    throw new SystemException($"Failed to set lockout state for the user {user.Id}.");
                }

                if (value)
                {
                    identityResult = await this.UserManager.SetLockoutEndDateAsync(user, null);
                    if (!identityResult.Succeeded)
                    {
                        throw new SystemException($"Failed to set lockout end date for the user {user.Id}.");
                    }
                }

                identityResult = await this.UserManager.UpdateAsync(user);
                if (!identityResult.Succeeded)
                {
                    throw new SystemException($"Failed to update information for the user {user.Id}.");
                }

                return await this.PackageResultAsync<VoidResponse>();
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex.Message, ex);
                return await this.PackageResultAsync<VoidResponse>(ex);
            }
        }

        [HttpPost]
        public async Task<JsonResult> DeleteAccount(string userId)
        {
            try
            {
                //this._logger.LogInformation($"Calling [{nameof(CloseAccount)}] with [phoneNumber = {phoneNumber}] and [captchas = {captchas}]...");
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user == null)
                {
                    throw new UnauthorizedAccessException($"Failed to find the user {userId}.");
                }
                //var signinResult = await this.SignInManager.CaptchasSignInAsync(user, captchas, false, true);
                //if (!signinResult.Succeeded && !this.ShouldSkipAthentication(phoneNumber))
                //{
                //    throw new UnauthorizedAccessException($"The user {phoneNumber} failed to login with captchas {captchas}.");
                //}

                var logins = user.Logins;
                foreach (var login in logins.ToList())
                {
                    var result = await this.UserManager.RemoveLoginAsync(user, login.LoginProvider, login.ProviderKey);
                    if (!result.Succeeded)
                    {
                        throw new SystemException($"Failed to RemoveFromRoleAsync the user {user.Id}.");
                    }
                }

                var rolesForUser = await this.UserManager.GetRolesAsync(user);
                if (rolesForUser.Count() > 0)
                {
                    foreach (var item in rolesForUser.ToList())
                    {
                        var result = await this.UserManager.RemoveFromRoleAsync(user, item);
                        if (!result.Succeeded)
                        {
                            throw new SystemException($"Failed to RemoveFromRoleAsync the user {user.Id}.");
                        }
                    }
                }

                var identityResult = await this.UserManager.DeleteAsync(user);
                if (!identityResult.Succeeded)
                {
                    throw new SystemException($"Failed to delete the user {user.Id}.");
                }
                return await this.PackageResultAsync<VoidResponse>();
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex.Message, ex);
                return await this.PackageResultAsync<VoidResponse>(ex);
            }
        }

        public async Task<JsonResult> CloseAccount(string phoneNumber, string captchas)
        {
            try
            {
                //this._logger.LogInformation($"Calling [{nameof(CloseAccount)}] with [phoneNumber = {phoneNumber}] and [captchas = {captchas}]...");
                var user = await this.UserManager.FindByPhoneNumberAsync(phoneNumber);
                if (user == null)
                {
                    throw new UnauthorizedAccessException($"Failed to find the user {phoneNumber}.");
                }
                var signinResult = await this.SignInManager.CaptchasSignInAsync(user, captchas, false, false);
                if (!signinResult.Succeeded && !(await this.ShouldSkipAthenticationAsync(phoneNumber)))
                {
                    throw new UnauthorizedAccessException($"The user {phoneNumber} failed to login with captchas {captchas}.");
                }

                var logins = user.Logins;
                foreach (var login in logins.ToList())
                {
                    var result = await this.UserManager.RemoveLoginAsync(user, login.LoginProvider, login.ProviderKey);
                    if (!result.Succeeded)
                    {
                        throw new SystemException($"Failed to RemoveFromRoleAsync the user {user.Id}.");
                    }
                }

                var rolesForUser = await this.UserManager.GetRolesAsync(user);
                if (rolesForUser.Count() > 0)
                {
                    foreach (var item in rolesForUser.ToList())
                    {
                        var result = await this.UserManager.RemoveFromRoleAsync(user, item);
                        if (!result.Succeeded)
                        {
                            throw new SystemException($"Failed to RemoveFromRoleAsync the user {user.Id}.");
                        }
                    }
                }

                var identityResult = await this.UserManager.DeleteAsync(user);
                if (!identityResult.Succeeded)
                {
                    throw new SystemException($"Failed to delete the user {user.Id}.");
                }
                return await this.PackageResultAsync<VoidResponse>();
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex.Message, ex);
                return await this.PackageResultAsync<VoidResponse>(ex);
            }
        }

        private async Task<bool> ShouldSkipAthenticationAsync(string phoneNumber, ApplicationUser user = null)
        {
            //return (!this._hostingEnvironment.IsProduction()) && string.Equals(phoneNumber, this.DefaultAdminPhoneNumber);
            user = user ?? this.DataContext.Users.AsNoTracking().FirstOrDefault(x => x.PhoneNumber == phoneNumber);
            if (user != null)
            {
                var appSecrect = default(StringValues);
                var appId = default(StringValues);
                if (this.HttpContext.Request.Headers.TryGetValue("AppSecrect", out appSecrect) &&
                    this.HttpContext.Request.Headers.TryGetValue("AppId", out appId))
                {
                    var valid = await this.ValidateWebApiCredentialAsync(appId.ToString(), appSecrect.ToString());
                    if (valid)
                    {
                        return true;
                    }
                }
                if (await this.UserManager.IsInRoleAsync(user, ApplicationRole.Staff))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            return string.Equals(phoneNumber, this.DefaultAdminPhoneNumber);
            //return true;
        }

    }

}